Skip to content

feat(cli,api): difyctl version probes server and reports compat verdict#36356

Merged
lin-snow merged 4 commits into
langgenius:feat/clifrom
lin-snow:feat/cli-version
May 20, 2026
Merged

feat(cli,api): difyctl version probes server and reports compat verdict#36356
lin-snow merged 4 commits into
langgenius:feat/clifrom
lin-snow:feat/cli-version

Conversation

@lin-snow
Copy link
Copy Markdown
Contributor

@lin-snow lin-snow commented May 18, 2026

Summary

  • New GET /openapi/v1/_version meta endpoint (unauth, mirrors _health) exposes server { version, edition }.
  • difyctl version becomes a kubectl-style three-block report (client / server / compat). Flags: -o text|json|yaml, --client to skip the probe, --short for scripting, --check-compat exits 64 when status ≠ compatible (stdout still gets the full envelope first so pipelines work).
  • Every authed command now probes /_version in the background and, on the first unsupported verdict within 24h, prints one yellow stderr line nudging the user to run difyctl version. Suppressed when stdout isn't a TTY or format is json|yaml|name. Cache cache/nudge.json stores only lastWarnedAt (no verdict caching = no stale-warn ambiguity); writes are atomic temp+rename with merge-on-write for concurrent CLIs. Probe failures are silenced — nudge never affects the host command.
  • OpenAPI types moved to the shared @dify/contracts/api/openapi/types.gen package, completing the migration started in chore: switched to shared contract #36380.

Probe degrades gracefully — unreachable / unauth / non-semver / empty → compat.status = unknown, default exit 0. Only --check-compat treats unsupported and unknown as failures.

Closed three-block envelope (top-level keys stable, additive within blocks):

{
  "client":  { "version", "commit", "buildDate", "channel", "platform", "arch" },
  "server":  { "endpoint", "reachable", "version?", "edition?" },
  "compat":  { "minDify", "maxDify", "status", "detail" }
}

Two single sources of truth: cli/package.json#difyctl.compat (the tested Dify range) and api/pyproject.toml#[project].version (the actual Dify version, also driving docker tags / PyPI).

770/770 tests green (CLI + API).

Screenshots

CLI change — no UI; sample output:

difyctl version against an unsupported server
Client:
  Version:   1.14.1-... (channel: rc)
  Commit:    4c36baa (built 2026-05-19T...)
  Platform:  darwin/arm64
  Compat:    dify >=1.14.0, <=1.15.0

Server:
  Endpoint:  http://127.0.0.1:4567
  Version:   1.6.4 (cloud)

Compatibility: incompatible — server 1.6.4 outside [1.14.0, 1.15.0]
Authed command with auto-nudge banner (first time within 24h)
warning: difyctl 1.14.1-... may be incompatible with server 1.6.4 (tested: 1.14.0..1.15.0). Run `difyctl version` for details.

ID    NAME     ROLE   STATUS  CURRENT
ws-1  Default  owner  normal  *

Banner appears once per host per 24h on stderr; throttled re-runs are silent.

Checklist

  • This change requires a documentation update, included: Dify Document
  • I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.
  • I ran make lint && make type-check (backend) and cd web && pnpm exec vp staged (frontend) to appease the lint gods

@lin-snow lin-snow requested review from a team, QuantumGhost and laipz8200 as code owners May 18, 2026 15:29
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. javascript Pull requests that update javascript code labels May 18, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 18, 2026

Pyrefly Type Coverage

Metric Base PR Delta
Type coverage 0.00% 44.45% +44.45%
Strict coverage 0.00% 43.98% +43.98%
Typed symbols 0 22,950 +22,950
Untyped symbols 0 28,988 +28,988
Modules 0 2646 +2,646

@lin-snow lin-snow self-assigned this May 18, 2026
@lin-snow lin-snow force-pushed the feat/cli-version branch 2 times, most recently from 9976c37 to a0ab9d5 Compare May 19, 2026 03:21
Copy link
Copy Markdown
Contributor

@GareArc GareArc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runVersionProbe() only fires when the user explicitly runs difyctl version. Consider adding a lightweight staleness-based background check that surfaces compat warnings on any command. (e.g. on each command run, if last update time is older than 24h, fire /_version and update it.)

@lin-snow lin-snow force-pushed the feat/cli-version branch 2 times, most recently from 84b4e31 to 8876356 Compare May 19, 2026 08:29
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels May 19, 2026
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels May 19, 2026
@github-actions github-actions Bot added the web This relates to changes on the web. label May 19, 2026
@lin-snow lin-snow requested review from GareArc and removed request for a team, QuantumGhost, Yeuoly, crazywoola and laipz8200 May 19, 2026 08:54
@lin-snow lin-snow force-pushed the feat/cli-version branch from b243a2f to 4c36baa Compare May 19, 2026 10:06
lin-snow and others added 4 commits May 20, 2026 10:19
Adds GET /openapi/v1/_version (no auth, mirrors _health) and reshapes
difyctl version into a kubectl-style three-block report (client / server /
compat) with -o text|json|yaml, --client to skip the probe, --short for
scripting, and --check-compat to exit 64 when status != compatible.

resolveBuildInfo now also falls back to package.json#difyctl.compat so
pnpm dev/build/test all carry a real compat range without needing the
release pipeline env vars.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every command that goes through buildAuthedContext (get / describe / run /
auth-devices / ...) now silently refreshes a per-host compat snapshot at
most every 24 h and shows one stderr line if the cached verdict is
'unsupported'. Banner is suppressed in pipeline mode (-o json|yaml|name),
when stdout is not a TTY, on first-ever invocation against a host, and
for 24 h after the last warning. No new env vars, no new flags, no new
config keys — purely behavior driven by what the user is already doing.

The existing 'difyctl version' command (explicit probe) is unaffected; it
does not go through authedCtx and keeps its own contract intact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aligns with the post-langgenius#36380 architecture: openapi-namespace types now
live in packages/contracts/generated/api/openapi/, generated alongside
console/service/web from the same Swagger spec. CLI imports rewrite to
'@dify/contracts/api/openapi/types.gen', the local
cli/src/types/data-contracts.ts is removed, and the orphan
'sync-models' npm script (its .sh file was already deleted upstream) is
dropped.

Behavioral: ServerVersionResponse + every other openapi type is now
sourced from the shared package. Zero runtime change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ghter probe contract

Address review comments on the version + auto-nudge feature:

cache:
- Replace `compat-snapshot` (verdict + server-version cache) with `nudge-store`
  which only persists `lastWarnedAt` per host. The nudge probe is now realtime;
  the cache only throttles how often we warn. Removes the lost-update / clock-
  skew / stale-warn classes of bugs in one stroke, net −408 LoC.
- `markWarned` re-reads disk inside the write cycle so concurrent CLI
  invocations on different hosts can't clobber each other's timestamps.

nudge:
- Throttle-first decision tree: cheap checks (TTY, output format, silence
  window) run before any I/O. Probe failures are silently swallowed and never
  drive a warning.
- `clientVersion` is now injected via `NudgeDeps` instead of reached through
  the `versionInfo` global, keeping the banner pure.

probe / meta client:
- Drop `bearer` from `MetaProbe` signature. `/openapi/v1/_version` is
  intentionally unauth (mirrors `_health`); the bearer parameter was dead code.
- Move `META_PROBE_TIMEOUT_MS` from inside `MetaClient.serverVersion()` to
  the `createClient(...)` call at both probe sites (version command and
  per-command nudge). Both now also disable retry, so the default 30s × 3
  budget never leaks into version probes.

compat:
- Clamp malformed `serverVersion` strings to 80 chars before quoting them in
  the verdict detail, so a hostile server response can't flood stderr.

version command:
- Emit the formatted report on stdout *before* exiting on `--check-compat`.
  This makes `difyctl version -o json --check-compat | jq ...` work the same
  way on the failure path as on success — pipelines get the JSON envelope,
  stderr gets the one-line reason, exit code signals the verdict.
@lin-snow lin-snow force-pushed the feat/cli-version branch from 4c36baa to 798d007 Compare May 20, 2026 02:23
Copy link
Copy Markdown
Contributor

@GareArc GareArc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label May 20, 2026
@lin-snow lin-snow merged commit 5381452 into langgenius:feat/cli May 20, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript Pull requests that update javascript code lgtm This PR has been approved by a maintainer size:XL This PR changes 500-999 lines, ignoring generated files. web This relates to changes on the web.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants